/*
 * Copyright 2011 Marcin Blazejewski
 *
 * This file is part of Ground0.
 *
 * Ground0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ground0 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ground0. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef MIDLEV_H_
#define MIDLEV_H_

#include "geometry.hpp"
#include "quat.hpp"

#include <list>
#include <vector>
#include <string>
#include <GL/gl.h>
#include <GL/glu.h>



/**
 * Basic geometry block: sets of faces with materials, normals and texture data.
 *
 * This class holds a 3D mesh arranged by sets of faces. Each face set can have 
 * a different material assigned to it, but all faces within a set have the same material.
 * Face sets and corresponding maetrials are held in 2 arrays: faces and materials.<br/>
 * Each face set is an array of Face objects with pointers to vertices, normals and tex coords
 * owned and held by this object's arrays: vertices, normals and texcoords.<br/>
 * This class is responsible for destroying the geometry objects associated with the mesh.<br/>
 *
 * The class can draw itself using opengl methods. A display list is used for each face set.
 * Face normals can be calculated.
 * 
 * @sa Face
 * @sa Vector
 * @sa Material
 * @sa BlockBuilder
 */
class Block {
private:
    /// is the display list ready?
    bool                      ready;
    /// display list for each face set from faces/materials arrays
    vector<GLuint>            disp_lists;

public:
    /// vertices used in this block's faces
    vector<Vector*>           vertices;
    /// face or vector normals used by this block's faces
    vector<Vector*>           normals;
    /// texture coordinates (optional) used by this block's faces
    vector<Vector*>           texcoords;
    /// array of face sets; each face set is an array of faces
    vector<vector<Face> >     faces;
    /// array of materials corresponding to face sets in faces array
    vector<class Material*>   materials;
    
    Block();
    ~Block();

    /// prepare display lists for all face sets
    void                      prepare();
    /// draw the mesh
    void                      draw();
    /// calculate normals for each face that doesn't already have normals (has null pointers)
    void                      calc_normals();    
};

std::ostream& operator<<(std::ostream&, const Block &b);

/**
 * Helper class for easy Block object creation.
 * 
 * Adding a new face looks if there are appropriate vertices already in block's vertices array.
 * If so, the existing vertices are used. Otherwise new vertex objects are created and added
 * to block's vertices array.<br/>
 *
 * It is sufficient to use add_face methods. It will automatically add the necessary vertices, normals
 * and tex coords. The rest of add_* methods can be used if the data is available to speed up the process.
 *
 * @sa Block
 */
class BlockBuilder {
private:
    Block                    *block;
    /// index of last face set added
    int                       current_faceset;
public:
    /// start building a new block _b
    /// if _b is null, a new object is created
    BlockBuilder(Block *_b=NULL);

    /// return the block created by this builder
    Block*                    get_block() { return block; }
    /// add a new vertex
    void                      add_vertex(Vector &c);
    /// add a new normal vector
    void                      add_normal(Vector &c);
    /// add texture coordinate
    void                      add_texcoord(Vector &c);
    /// add a new face set with material m
    /// @return the new face set index to be used in add_face
    int                       add_face_set(Material *m);
    /// add face to face f set index faceset
    void                      add_face(int faceset, const Face &f);
    /// add face f to last face set that was added
    void                      add_face(const Face &f);
};


/// point light source
class Light {
public:
    /// diffuse color
    float                    color[4];
    /// light position
    float                    pos[4];
    /// is light enabled
    bool                     on;

    /// create a new light with each color component set to v
    Light(float v=0.0f);
    /// adjust position by x,y,z
    void                     move(float x, float y, float z);
    /// set diffuse color
    void                     set_color(float r, float g, float b);
};


/// Simple material
class Material {
public:
    /// ambient color
    float                    ambient[4];
    /// diffuse color
    float                    diffuse[4];
    /// specular color
    float                    specular[4];
    /// OpenGL texture id
    GLuint                   texture;
    /// true if texture is used at all
    bool                     use_tex;

    /// create with diffuse color r,g,b
    Material(float r=0, float g=0, float b=0);
    ~Material();

    /// load and use texture from file
    void                     set_texture(const char *);
    /// set current opengl material and texture (if used)
    void                     apply();
};


typedef struct {
    float                    x, y, z;
    float                    vx, vy, vz;
    int                      life;
} Particle;

/// simple particle set
class PartSet {
private:

public:
    /// list of particles
    list<Particle>           particles;
    /// how fast the particle decays (linear, each tick decreases life by decay_rate * dt)
    float                    decay_rate;
    /// starting life of new particles
    int                      new_life;

    /// create particle set with decay rate and default particle life
    PartSet(float decay_rate, int new_life=1);
    ~PartSet();

    /// add new particle at given position, with given velocity and life
    /// life defaults to new_life fiield
    void                     add_particle(int x0, int y0, int z0,
                                          int vx, int vy, int vz,
                                          int life=0);

    /// draw all particles
    void                     draw();
    /// simulation tick: move particles, decay and destroy dead particles
    void                     tick(const float dt);
};


#endif
